package dev.ideas.funkyj.support;

import dev.ideas.funkyj.function.F;
import dev.ideas.funkyj.function.F0;
import dev.ideas.funkyj.function.F2;
import dev.ideas.funkyj.function.P;

import java.util.*;

import static dev.ideas.funkyj.support.Stuff.join;

/**
 */
@SuppressWarnings({"unchecked", "UnusedDeclaration"})
/*leave the T generic here, although UnusedDeclaration, so that import static of Sequence results in this namespace imported statically as well*/
//todo when intellij collapses the closures here it looks much better, but unfortunately it does not collapse closures with constructors
public abstract class PredicateSupport<T> {

    //general
    public static final F identity = new F("I") { public final Object apply(final Object o) { return o; } };
    public static <X, Y extends X> F<X, Y> identity() { return new F<X, Y>("I") { public Y apply(X x) { return (Y) x; } }; }
    public static P<Boolean> isTrue = new P<Boolean>("true") { public Boolean apply(Boolean aBoolean) { return aBoolean; } };
    public static final F<Object, String> string = new F<Object, String>("toString") {
        public String apply(Object object) { return object.toString(); }
    };
    public static final F<String,Integer> integer = new F<String, Integer>("toInteger") {
        public Integer apply(String s) {
            return Integer.valueOf(s);
        }
    };
    public static <X, Y> F<? super X, Y> maybe(final F<? super X, Y> f) { return new F<X, Y>("?" + f) { @Override public Y apply(X x) { return x != null ? f.apply(x) : null; } }; }
    public static <X> F<? super X, X> maybe(final P<? super X> p) { return new F<X, X>("?" + p) { @Override public X apply(X x) { return x != null ? (p.apply(x) ? x : null) : null; } }; }

    //logic
    public static <T> P<T> and(final P<? super T>... predicates) { return new P<T>("(" + join(" and ", predicates) + ")") { public Boolean apply(T t) { for (P<? super T> predicate : predicates) if (!predicate.apply(t)) return false; return true; } }; }
    public static <T> P<T> or(final P<? super T>... predicates) { return new P<T>("(" + join(" or ", predicates) + ")") { public Boolean apply(T t) { for (P<? super T> predicate : predicates) if (predicate.apply(t)) return true; return false; } }; }
    public static <T> P<T> not(final P<T> predicate) { return predicate.not(); }
    public static <T> P<T> equal(final T t1) { return new P<T>("= " + t1) { public Boolean apply(T t) { return t.equals(t1); } }; }
    public static <T> P<T> equalAny(final T... ts) { return new P<T>("= any (" + join(", ", ts) + ")") { public Boolean apply(T t) { for (T t1 : ts) if (t.equals(t1)) return true; return false; } }; }
    public static <X, Y> P<? super X> is(final F<X, Y> transformer, final P<Y> predicate) { return new P<X>(transformer + " " + predicate) { public Boolean apply(X x) { return predicate.apply(transformer.apply(x)); } }; }

    //compare
    public static <T, R extends Comparable<R>> P<T> bigger(final F<T, R> f, final R r) { return new P<T>(f + " > " + r) { public Boolean apply(T t) { return f.apply(t).compareTo(r) >= 1; } }; }
    public static <T, R extends Comparable<R>> P<T> smallerOrEqual(final F<T, R> f, final R r) { return new P<T>(f + " <= " + r) { public Boolean apply(T t) { return f.apply(t).compareTo(r) <= 0; } }; }
    public static <T, R extends Comparable<R>> P<T> smaller(final F<T, R> f, final R r) { return new P<T>(f + " < " + r) { public Boolean apply(T t) { return f.apply(t).compareTo(r) < 0; } }; }
    public static <T extends Comparable<T>> P<T> smallerThan(final T level) { return new P<T>("< " + level) { public Boolean apply(T t) { return t.compareTo(level) < 0; } }; }
    public static <T, R extends Comparable<R>> P<T> biggerOrEqual(final F<T, R> f, final R r) { return new P<T>(f + " >= " + r) { public Boolean apply(T t) { return f.apply(t).compareTo(r) >= 0; } }; }
    public static <T, R extends Comparable<R>> P<T> between(final F<T, R> f, final R r1, final R r2) { return new P<T>(r1 + " < " + f + " < " + r2) { public Boolean apply(T t) { final R v = f.apply(t); return v.compareTo(r1) > 0 && v.compareTo(r2) < 0; } }; }
    public static <T, R extends Comparable<R>> P<T> betweenOrEqual(final F<T, R> f, final R r1, final R r2) { return new P<T>(r1 + " <= " + f + " <= " + r2) { public Boolean apply(T t) { final R v = f.apply(t); return v.compareTo(r1) >= 0 && v.compareTo(r2) <= 0; } }; }
    public static <T extends Comparable<T>> P<T> biggerThan(final T level) { return new P<T>("> " + level) { public Boolean apply(T t) { return t.compareTo(level) > 0; } }; }
    public static final Comparator natural = new Comparator<Comparable>() { public int compare(Comparable o1, Comparable o2) { return o1.compareTo(o2); } };
    public static final Comparator hashcode = new Comparator() { public int compare(Object o1, Object o2) { return ((Integer) o1.hashCode()).compareTo(o2.hashCode()); } };
    public static final Comparator desc = new Comparator() { public int compare(Object o1, Object o2) { return (((Comparable) o2)).compareTo(o1); } };
    public static <T extends Comparable<T>> F<T, Integer> compare(final T t1) { return new F<T, Integer>() { public Integer apply(T t) { return t.compareTo(t1); } }; }

    //in
    public static <T> P<T> in(final Collection<T> collection) { return new P<T>("in " + collection) { public Boolean apply(T t) { return collection != null && collection.contains(t); } }; }
    public static <T> P<T> in(final T... ts) { return in(Arrays.asList(ts)); }
    public static <T> P<T> notIn(final Collection<T> collection) { return not(in(collection)); }
    public static <T> P<T> notIn(final T... ts) { return not(in(ts)); }

    //number
    public static final P<Integer> even = new P<Integer>("even") { public Boolean apply(Integer number) { return number % 2 == 0; } };
    public static final P<Integer> odd = new P<Integer>("odd") { public Boolean apply(Integer number) { return number % 2 == 1; } };
    public static final F2<Integer, Integer, Integer> sum = new F2<Integer, Integer, Integer>("sum") { @Override public Integer apply(Integer i1, Integer i2) { return i1 + i2; } };
    public static final F2<Integer, Integer, Integer> mulI = new F2<Integer, Integer, Integer>("mul") { @Override public Integer apply(Integer i1, Integer i2) { return i1 * i2; } };
    public static final F2<Integer, Integer, Integer> minI = new F2<Integer, Integer, Integer>("min") { @Override public Integer apply(Integer i1, Integer i2) { return Math.min(i1, i2); } };
    public static final F2<Integer, Integer, Integer> maxI = new F2<Integer, Integer, Integer>("max") { @Override public Integer apply(Integer i1, Integer i2) { return Math.max(i1, i2); } };
    public static final F2<Float, Integer, Float> mul = new F2<Float, Integer, Float>("mul") { @Override public Float apply(Float aFloat, Integer integer) { return aFloat * integer; } };

    //string
    public static P<String> startsWith(final String prefix) { return new P<String>("startsWith " + prefix) { public Boolean apply(String s) { return s.startsWith(prefix); } }; }
    public static final F<String,Integer> length = new F<String, Integer>("length") { @Override public Integer apply(String s) { return s.length(); } };

    //sugar support
    public static Collection<Integer> range(final int t1, final int t2) {
        return new AbstractCollection<Integer>() {
            @Override public Iterator<Integer> iterator() {
                return new Iterator<Integer>() {
                    int c = t1;
                    public boolean hasNext() { return c <= t2; }
                    public Integer next() { return c++; }
                    public void remove() { throw new UnsupportedOperationException(); }
                    @Override public String toString() { return "[" + t1 + " - " + t2 + "] (" + c + ")"; }
                };
            }
            @Override public int size() { return t2 - t1; }
            @Override public String toString() { return "[" + t1 + " - " + t2 + "]"; }
        };
    }

    public static <X> F0<X> lazy(final F0<X> factory) {
        return new F0<X>() {
            private F0<X> delegate = new F0<X>() {
                @Override public X get() {
                    //first time return value from factory
                    final X value = factory.get();
                    //next return the X value
                    return (delegate = new F0<X>() {
                        @Override public X get() {
                            return value;
                        }
                    }).get();
                }
            };

            @Override public X get() { return delegate.get(); }
        };
    }
    
    //todo support arrays/collections (x.y[0].z) and else (?)
    /** this is not strongly typed, you'd better create a specialized lambda $name
     * <p> also $ closes on the class of x when first used, so don't use the same $ instance on different class types*/
    @Deprecated public static <Y> F<Object, Y> $(final String ognl) {
      return new F<Object, Y>("$" + ognl) {
          public Y apply(Object x) { return delegate.apply(x); }

          private F<Object, Y> delegate = new F<Object, Y>() {
              @Override public Y apply(Object x) {
                  return (delegate = $.create(x, ognl)).apply(x);
              }
          };
      };
    }

}
